function isObject(value) {
  return Object.prototype.toString.call(value) === '[object Object]'
}

function isDate(value) {
  return Object.prototype.toString.call(value) === '[object Date]'
}

function isFunction(value) {
  return Object.prototype.toString.call(value) === '[object Function]'
}
function isString(value) {
  return Object.prototype.toString.call(value) === '[object String]'
}
function isArray(value) {
  return Object.prototype.toString.call(value) === '[object Array]'
}
function isNumber(value) {
  return Object.prototype.toString.call(value) === '[object Number]'
}
function isNull(value) {
  return Object.prototype.toString.call(value) === '[object Null]'
}
function isBoolean(value) {
  return Object.prototype.toString.call(value) === '[object Boolean]'
}
function isUndefined(value) {
  return Object.prototype.toString.call(value) === '[object Undefined]'
}
function isSymbol(value) {
  return Object.prototype.toString.call(value) === '[object Symbol]'
}

function isWeakMap(value) {
  return Object.prototype.toString.call(value) === '[object WeakMap]'
}

function isWeakSet(value) {
  return Object.prototype.toString.call(value) === '[object WeakSet]'
}

function isMap(value) {
  return Object.prototype.toString.call(value) === '[object Map]'
}

function isSet(value) {
  return Object.prototype.toString.call(value) === '[object Set]'
}
function isRegExp(value) {
  return Object.prototype.toString.call(value) === '[object RegExp]'
}

function cloneDeep(target, weakMap = new WeakMap()) {
  if (
    isFunction(target) ||
    isNull(target) ||
    isBoolean(target) ||
    isNumber(target) ||
    isUndefined(target) ||
    isString(target) ||
    /**
     * WeakMap 和 WeakSet,存储的都是一些「临时」的值，只要引用次数为0，会被垃圾回收机制自动回收
     * 这个时机是不可预测的，所以一个WeakMap 和 WeakSet 里面目前有多少个成员也是不可预测的
     * ECMAScript 也规定WeakMap 和 WeakSet 不可遍历
     * 所以WeakMap 和 WeakSet 是无法拷贝。
     * 借鉴loadsh.cloneDeep 直接返回原对象或{}
     */
    isWeakSet(target) ||
    isWeakMap(target)
  ) {
    return target
  }

  if (isSymbol(target)) {
    return Symbol(target.description)
  }

  if (isMap(target) || isDate(target) || isSet(target) || isRegExp(target)) {
    const arrClass = [Map, Date, Set, RegExp]

    for (const Cls of arrClass) {
      if (target instanceof Cls) return new Cls(target)
    }
  }

  // 解决循环引用的问题
  if (weakMap.has(target)) return weakMap.get(target)

  if (isObject(target) || isArray(target)) {
    const newTarget = isArray(target) ? [] : {}

    weakMap.set(target, newTarget)

    // !!for in不建议遍历数组
    for (const key in target) {
      // 忽视原型链上继承的属性
      if (Object.hasOwnProperty.call(target, key)) {
        newTarget[key] = cloneDeep(target[key], weakMap)
      }
    }

    return newTarget
  }
}

export default cloneDeep
